package com.kirichof.matchreminder.batchjob;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import net.fortuna.ical4j.data.CalendarBuilder;
import net.fortuna.ical4j.data.ParserException;
import net.fortuna.ical4j.filter.Filter;
import net.fortuna.ical4j.filter.PeriodRule;
import net.fortuna.ical4j.filter.Rule;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.DateTime;
import net.fortuna.ical4j.model.Dur;
import net.fortuna.ical4j.model.Period;

import com.kirichof.matchreminder.core.Log;
import com.kirichof.matchreminder.core.SendMail;

public class CalendarEventScannJob implements Runnable {

	/** Flag to indicate whether the thread has to stop or not */
	private volatile boolean done = false;

	/** Milliseconds the thread gets sleep between runs */
	private int millis = 60 * 1000; // <- Every minute!

	/** Les notificacions s'enviaràn a les 7 del matí */
	private int horaEnviament = 7;

	/** Minut de la hora en la que es volen enviar les notificacions */
	private int minutEnviament = 0;

	/**
	 * To be called when shutting down the thread
	 */
	public void shutdown() {
		done = true;
	}

	/**
	 * Main execution of the thread
	 */
	public synchronized void run() {
		Log.info("Entering run method in CalendarEventScannJob thread");
		while (!done) {
			try {
				doProcess();
				Thread.sleep(millis);
			} catch (InterruptedException e) {
				Log.getLogger().error(
						"Error while sleeping CalendarEventScannJob thread by "
								+ millis + " milliseconds");
			}
		}
		// This code may not be reached
		Log.getLogger().info("Leaving run method");
	}

	/**
	 * This method checks if its time to check the Calendar.
	 */
	private void doProcess() {

		// If its time
		if (checkTimeToSendNotifications()) {
			// Lets take a look at the calendar
			scannCalendarAndSendNotifications();
		}
	}

	/**
	 * This method returns true if its time to check the calendar for events.
	 * 
	 * @return true if its time to send notifications!
	 */
	private boolean checkTimeToSendNotifications() {

		/** Obtenim la hora actual */
		java.util.Calendar calendar = java.util.Calendar.getInstance();
		int horaActual = calendar.get(java.util.Calendar.HOUR_OF_DAY);
		int minutActual = calendar.get(java.util.Calendar.MINUTE);

		return horaActual == horaEnviament && minutActual == minutEnviament;
	}

	/**
	 * This method reloads the calendar events and sends notifications if an
	 * event is coming soon.
	 */
	private void scannCalendarAndSendNotifications() {

		// 1: Read calendar's .ics
		Calendar matchsCalendar = getCalendar();

		// 2: Check if there's any event coming soon (in the next 5 days)
		Component partit = getPartitEn5Dies(matchsCalendar);
		if (partit != null && !notificationAlreadySentToday()) {
			sendNotificationToTeamMates();
		}

	}

	/**
	 * This method returns true if a notification has already been sent today.
	 * False if not.
	 * 
	 * @return true if a notification has already been sent today. False if not.
	 */
	private boolean notificationAlreadySentToday() {
		// TODO: read from DDBB the date of the last notification action. If
		// it's today, then return true.
		return false;
	}

	/**
	 * Scanns teammate list and sends notifications (poll)
	 */
	private void sendNotificationToTeamMates() {

		// 1. Get list of mail recievers (team mates)
		List<String> emails = getTeammateEmails();

		for (Iterator iterator = emails.iterator(); iterator.hasNext();) {
			String string = (String) iterator.next();
			SendMail.sendMail("lluisunyol@gmail.com", string, "Asunto",
					"Cos de l'email", "lluisunyol@gmail.com", "kiriisthebest");
		}

	}

	/**
	 * Gets the list of teammate emails from database
	 * 
	 * @return list of teammate emails from database
	 */
	private List<String> getTeammateEmails() {
		// TODO: get list from SQLite!
		List<String> emails = new ArrayList<String>();
		emails.add("kirichof@gmail.com");
		emails.add("lluisunyol@gmail.com");
		return emails;
	}

	/**
	 * Returns a <code>Component</code> if there's any event coming in the
	 * following 5 days from today.
	 * 
	 * @param matchsCalendar
	 * 
	 * @return a <code>Component</code> if there's any event coming in the
	 *         following 5 days from today. <code>null</code> otherwise
	 */
	private Component getPartitEn5Dies(Calendar matchsCalendar) {
		Component c = null;
		Period cincDies = crearPeriode(5);
		Collection<Component> partitsPropers = getCalendarEvents(
				matchsCalendar, cincDies);
		Iterator<Component> iterator = partitsPropers.iterator();
		if (iterator.hasNext()) {
			return iterator.next();
		} else {
			return null;
		}
	}

	/**
	 * Gets the calendar from the Internet
	 * 
	 * @return
	 */
	private Calendar getCalendar() {
		// For the moment, we'll get it from local file
		FileInputStream fin;
		Calendar calendar = null;
		try {
			fin = new FileInputStream("/opt/300/calendari300.ics");
			CalendarBuilder builder = new CalendarBuilder();
			calendar = builder.build(fin);
		} catch (FileNotFoundException e) {
			// TODO This must be changed: get it from the internet, not from
			// filesystem!
			Log.getLogger().error(
					"Something happened when getting the calendar", e);
		} catch (IOException | ParserException e) {
			Log.getLogger().error(
					"An error occurred when parsin the ICS stream: "
							+ e.getMessage(), e);
		}
		return calendar;
	}

	/**
	 * Retorna el període de dies des d'avui fins a passats
	 * <code>numDays<code> dies.
	 * 
	 * @param numDays
	 *            Nombre de dies a partir d'avui els quals es vol que contingui
	 *            el període.
	 * @return període de dies des d'avui fins a passats <code>numDays<code>
	 *         dies
	 */
	private static Period crearPeriode(int numDays) {
		java.util.Calendar today = java.util.Calendar.getInstance();
		today.set(java.util.Calendar.HOUR_OF_DAY, 0);
		today.clear(java.util.Calendar.MINUTE);
		today.clear(java.util.Calendar.SECOND);
		Period p = new Period(new DateTime(today.getTime()), new Dur(numDays,
				0, 0, 0));
		return p;
	}

	/**
	 * Retorna llistat d'events del calendari que cauen dins del periode
	 * establert
	 * 
	 * @param c
	 *            Calendari sobre el que cercar events
	 * @param p
	 *            Període a filtrar
	 * @return llistat d'events del calendari que cauen dins del periode
	 *         establert
	 */
	private static Collection getCalendarEvents(Calendar c, Period p) {
		Rule[] r = { new PeriodRule(p) };
		Filter f = new Filter(r, Filter.MATCH_ALL);
		Collection eventsDelPeriode = f.filter(c
				.getComponents(Component.VEVENT));
		return eventsDelPeriode;
	}
}
